/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * TrainingBook
 * Copyright (C) Arnaud Duval 2010 <arnaud.duval@gmail.com>
 * 
 * TrainingBook is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * TrainingBook is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <TrackSegment.hpp>
#include <Duration.hpp>
#include <iostream>
#include <cmath>

///@brief Default constructor
TrackSegment::TrackSegment()
{

}

///@brief Copy constructor
///@param seg TrackSegment object to copy
TrackSegment::TrackSegment(const TrackSegment& seg)
{
    points = seg.points;
}

///@brief Destructor
TrackSegment::~TrackSegment()
{

}

///@brief Overload of = operator
///@param seg TrackSegment object to copy
TrackSegment &TrackSegment::operator=(const TrackSegment& seg)
{
    points = seg.points;
    return (*this);
}

///@brief Read data form a XML Node
///@param node Pointer to XML node to read
bool TrackSegment::ReadXMLNode(xmlpp::Node* node)
{
    if(node->get_name() != "trkseg")
        return false;

    xmlpp::Node::NodeList listTrackPt = node->get_children();
    for(xmlpp::Node::NodeList::iterator iter = listTrackPt.begin(); iter != listTrackPt.end(); ++iter)
    {
        xmlpp::Node* isTrackPoint = dynamic_cast<const xmlpp::Node*>(*iter);
        if(isTrackPoint->get_name() == "trkpt")
        {
            TrackPoint newPoint;
            if(newPoint.ReadXMLNode(isTrackPoint))
                points.push_back(newPoint);
        }
    }
    return true;
}

///@brief Return length of the segment in kilometers
double TrackSegment::Length()
{
    double res = 0.;
    TrackPoint p1, p2;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            p1 = (*lit);
        else
        {
            p2 = (*lit);
            res += Distance(p1,p2);
            p1 = p2;
        }
    }
    return res;
}

///@brief Return duration of the segment
Duration TrackSegment::GetDuration()
{
    Duration result;
    std::list<TrackPoint>::iterator lstart (points.begin());
    std::list<TrackPoint>::reverse_iterator lend(points.rbegin());
    DateTime tstart = (*lstart).GetTime();
    DateTime tend = (*lend).GetTime();
    
    result.Difference(tend, tstart);
    return result;
}

///@brief return start date of the segment
DateTime TrackSegment::GetStartDate()
{
    DateTime result;
    std::list<TrackPoint>::iterator lstart(points.begin());
    result = (*lstart).GetTime();
    return result;
}

///@brief Return total ascent
double TrackSegment::TotalAscent()
{
    double res = 0.;
    TrackPoint p1, p2;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            p1 = (*lit);
        else
        {
            p2 = (*lit);
            if(p2.GetElevation() > p1.GetElevation())
                res += p2.GetElevation() - p1.GetElevation();
            p1 = p2;
        }
    }
    return res;
}

///@brief Return total descent
double TrackSegment::TotalDescent()
{
    double res = 0.;
    TrackPoint p1, p2;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            p1 = (*lit);
        else
        {
            p2 = (*lit);
            if(p2.GetElevation() < p1.GetElevation())
                res += p2.GetElevation() - p1.GetElevation();
            p1 = p2;
        }
    }
    return res;
}

///@brief Append points to a distance list
///@param res List to extend
void TrackSegment::AppendToDistanceList(std::list<double>& res)  
{
    if(res.empty())
        res.push_back(0.);
    double val = *(res.end());

    TrackPoint p1, p2;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            p1 = (*lit);
        else
        {
            p2 = (*lit);
            val += Distance(p1,p2);
            res.push_back(val);
            p1 = p2;
        }
    }
}

///@brief Append points to an elevation list
///@param res List to extend
void TrackSegment::AppendToElevationList(std::list<double>& res)  
{
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(!((lit == points.begin())&&(!res.empty())))
        {
            TrackPoint p = (*lit);
            res.push_back(p.GetElevation());
        }
    }
}

///@brief Append points to a speed list
///@param res List to extend
void TrackSegment::AppendToSpeedList(std::list<double>& res)  
{
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(!((lit == points.begin())&&(!res.empty())))
        {
            TrackPoint p = (*lit);
            res.push_back(p.GetSpeed());
        }
    }
}

///@brief Append points to a latitude list
///@param res List to extend
void TrackSegment::AppendToLatList(std::list<double>& res)
{
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(!((lit == points.begin())&&(!res.empty())))
        {
            TrackPoint p = (*lit);
            res.push_back(p.GetLat());
        }
    }
}

///@brief Append points to a longitude list
///@param res List to extend
void TrackSegment::AppendToLonList(std::list<double>& res)
{
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(!((lit == points.begin())&&(!res.empty())))
        {
            TrackPoint p = (*lit);
            res.push_back(p.GetLon());
        }
    }
}

///@brief Return minimum value of elevation
double TrackSegment::MinElevation()  
{
    double res = 100000.;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetElevation();
        if(val < res)
            res = val;
    }
    return res;
}

///@brief Return maximum value of elevation
double TrackSegment::MaxElevation()
{
    double res = -10000.;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetElevation();
        if(val > res)
            res = val;
    }
    return res;
}

///@brief Return maximum value of speed
double TrackSegment::MaxSpeed()
{
    double res = 0.;
    std::list<TrackPoint>::const_iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetSpeed();
        if(val > res)
            res = val;
    }
    return res;
}

///@brief Return maximum value of latitude
double TrackSegment::MaxLat()
{
    double res = -1000.;
    std::list<TrackPoint>::const_iterator lit(points.begin()), lend(points.end());
    for(; lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetLat();
        if(val > res)
            res = val;
    }
    return res;
}

///@brief Return minimum value of latitude
double TrackSegment::MinLat()
{
    double res = 1000.;
    std::list<TrackPoint>::const_iterator lit(points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetLat();
        if(val < res)
            res = val;
    }
    return res;
}

///@brief Return maximum value of longitude
double TrackSegment::MaxLon()
{
    double res = -1000.;
    std::list<TrackPoint>::const_iterator lit(points.begin()), lend(points.end());
    for(; lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetLon();
        if(val > res)
            res = val;
    }
    return res;
}

///@brief Return minimum value of longitude
double TrackSegment::MinLon()
{
    double res = 1000.;
    std::list<TrackPoint>::const_iterator lit(points.begin()), lend(points.end());
    for(; lit != lend ; ++lit)
    {
        TrackPoint p = (*lit);
        double val = p.GetLon();
        if(val < res)
            res = val;
    }
    return res;
}

///@brief Write to XML file
///@param fout out stream to XML file
void TrackSegment::WriteXML(std::ofstream &fout)
{
    std::list<TrackPoint>::iterator lit (points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        TrackPoint pt = (*lit);
        fout << "\t\t\t\t\t<trkpt lat=\"" << pt.GetLat() << "\" lon=\"" << pt.GetLon() << "\">\n";
        pt.WriteXML(fout);
        fout << "\t\t\t\t\t</trkpt>\n";
    }
}

///@brief Filter the track by keeping points separated by a given distance, return the number of suppressed points
///@param dist Minimum distance between the kept points in meters
unsigned int TrackSegment::FilterByDistance(double dist)
{
    unsigned int res = 0;
    TrackPoint refpt, pt;

    std::list<TrackPoint>::iterator lit(points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            refpt = (*lit);
        else
        {
            pt = (*lit);
            if((Distance(refpt, pt) < dist / 1000.)&&(pt != *(points.rbegin())))
            {
                lit = points.erase(lit);
                res++;
                --lit;
            }
            else
            {
                refpt = pt;
            }
        }
    }
    return res;
}

///@brief Filter the track by keeping points separated by a given elevation variation, return the number of suppressed points
///@param altvar Minimum elevation variation between the kept points in meters
unsigned int TrackSegment::FilterByElevationVariation(double altvar)
{
    unsigned int res = 0;
    TrackPoint refpt, pt;

    std::list<TrackPoint>::iterator lit(points.begin()), lend(points.end());
    for(;lit != lend ; ++lit)
    {
        if(lit == points.begin())
            refpt = (*lit);
        else
        {
            pt = (*lit);
            if((fabs(refpt.GetElevation() - pt.GetElevation()) < altvar)&&(pt != *(points.rbegin())))
            {
                lit = points.erase(lit);
                res++;
                --lit;
            }
            else
            {
                refpt = pt;
            }
        }
    }
    return res;
}

///@brief Smooth the elevation values with given buffer size
///@param bufsize Points taken for the sliding mean
void TrackSegment::SmoothElevation(unsigned int bufsize)
{
    std::list<double> buffer;
    std::list<double> smoothed;
    std::list<TrackPoint>::iterator buffer_end_pos;

    for(std::list<TrackPoint>::iterator lit = points.begin() ; lit != points.end() ; ++lit)
    {
        if(lit == points.begin())
        ///@brief Initial filling of the buffer
        {
            buffer_end_pos = lit;
            while((buffer.size() < bufsize / 2)&&(buffer_end_pos != points.end()))
            {
                buffer.push_back((*buffer_end_pos).GetElevation());
                ++buffer_end_pos;	
            }
        }
        else
        {
            ///@brief Addition of a new element to the buffer
            if(buffer_end_pos != points.end())
            {
                buffer.push_back((*buffer_end_pos).GetElevation());
                ++buffer_end_pos;
                if(buffer.size() > bufsize)
                buffer.pop_front();
            }
        }

        ///@brief Compute mean value of the buffer
        double val = 0.;
        for(std::list<double>::iterator mit = buffer.begin() ; mit != buffer.end() ; ++mit)
            val += *mit;
        val /= buffer.size();

        smoothed.push_back(val);
    }

    for(std::list<TrackPoint>::iterator lit = points.begin() ; lit != points.end() ; ++lit)
    {
        (*lit).SetElevation(*(smoothed.begin()));
        smoothed.pop_front();
        ///@warning the use of pop_front may be time-consuming
    }
}

///@brief Recompute the speed values
void TrackSegment::RecomputeSpeed()
{
    std::list<double> new_speed;
    TrackPoint lastPoint, currentPoint, nextPoint;

    for(std::list<TrackPoint>::iterator lit = points.begin() ; lit != points.end() ; ++lit)
    {
        if(lit == points.begin())
        {
            lastPoint = *lit;
            new_speed.push_back((*lit).GetSpeed());
        }
        else
        {
            std::list<TrackPoint>::iterator tempit = lit;
            ++tempit;
            if(tempit != points.end())
                nextPoint = *tempit;
            else
                nextPoint = *lit;
            currentPoint = *lit;
            Duration time_elapsed;
            time_elapsed.Difference(nextPoint.GetTime(), lastPoint.GetTime());
            double speedVal = (Distance(lastPoint, currentPoint) + Distance(currentPoint, nextPoint))*1000./time_elapsed.ValueInSeconds();
            new_speed.push_back(speedVal);

            lastPoint = currentPoint;
        }
    }

    for(std::list<TrackPoint>::iterator lit = points.begin() ; lit != points.end() ; ++lit)
    {
        (*lit).SetSpeed(*(new_speed.begin()));
        new_speed.pop_front();
        ///@warning the use of pop_front may be time-consuming
    }
}

///@brief Add a new point at the end of the segment
///@param p Point to add
void TrackSegment::AddPoint(TrackPoint p)
{
    points.push_back(p);
}

///@brief Write a part of the content of the track segment to the standard output for debug
void TrackSegment::WriteForDebug()
{
    for(std::list<TrackPoint>::iterator lit = points.begin() ;
        lit != points.end();
        ++lit)
        (*lit).WriteForDebug();
}